package main

import "image/color"

import "code.google.com/p/x-go-binding/ui/x11"
import "code.google.com/p/x-go-binding/ui"
import "fmt"
import "image"
import "image/draw"
import "io/ioutil"

import "code.google.com/p/freetype-go/freetype"
import "code.google.com/p/freetype-go/freetype/raster"
import "code.google.com/p/freetype-go/freetype/truetype"

import "edit/buffer"
import "keys"
import "panel"

import "regexp"
import "unicode"

type Pen struct {
	Font       *truetype.Font
	Ink        color.Color
	Background color.Color
}

var fontMap = map[string]*truetype.Font{}

func loadFont(fontName string) *truetype.Font {
	font := fontMap[fontName]
	if font == nil {
		fontfile := fontName // "/usr/share/fonts/liberation/LiberationMono-Regular.ttf"
		fontBytes, _ := ioutil.ReadFile(fontfile)
		font, _ = freetype.ParseFont(fontBytes)
		fontMap[fontName] = font
	}
	return font
}

func NewPen(name string, r, g, b int) *Pen {
	font := loadFont("/usr/share/fonts/liberation/" + name + ".ttf")
	colour := image.NewUniform(color.RGBA{uint8(r), uint8(g), uint8(b), 255})
	return &Pen{font, colour, image.White}
}

type Surface struct {
	context    *freetype.Context
	image      draw.Image
	background color.Color
}

const pointSize = 10
const dpi = 96

func (s *Surface) Init(image draw.Image, background color.Color) *Surface {
	if s == nil {
		s = new(Surface)
	}
	context := freetype.NewContext()
	context.SetDst(image)
	context.SetClip(image.Bounds())
	context.SetFontSize(pointSize)
	context.SetDPI(dpi)
	s.image = image
	s.context = context
	s.background = background
	return s
}

func (s *Surface) PutStringOver(p *Pen, text string, xy raster.Point, col int) {
	s.context.SetFont(p.Font)
	s.context.SetSrc(image.NewUniform(p.Ink))
	fudx := p.Font.HMetric(p.Font.Index('_')).AdvanceWidth
	dx := int(fudx) * pointSize * dpi * 256 / p.Font.UnitsPerEm() / 72
	//	
	_, _ = s.context.DrawString(text, xy.Add(raster.Point{raster.Fix32(col*dx - dx/2), 0}))
}

func (s *Surface) PutString(p *Pen, text string, xy raster.Point) (raster.Point, error) {
	s.context.SetFont(p.Font)
	s.context.SetSrc(image.NewUniform(p.Ink))
	//	
	x, y := int(xy.X>>8), int(xy.Y>>8)-12
	dx, dy := len(text)*8+800, 18
	bounds := image.Rectangle{image.Point{x, y}, image.Point{x + dx, y + dy}}
	draw.Draw(s.image, bounds, image.NewUniform(color.RGBA{255, 255, 255, 255}), image.ZP, draw.Src)
	//	
	after, err := s.context.DrawString(text, xy)
	if err != nil {
		fmt.Println("oops:", err)
	}
	return after, nil
}

type EditState struct {
	buffer       buffer.Buffer
	scrollOffset int
	Lines        int
	Line         int
	Col          int
}

type subimage struct {
	image  draw.Image
	offset image.Rectangle
	bounds image.Rectangle
}

func (s *subimage) At(x, y int) color.Color {
	return s.image.At(x, y)
}

func (s *subimage) Bounds() image.Rectangle {
	return s.bounds
}

func (s *subimage) ColorModel() color.Model {
	return s.image.ColorModel()
}

func (s *subimage) Set(x, y int, c color.Color) {
	s.image.Set(x+s.offset.Min.X, y+s.offset.Min.Y, c)
}

func SubImage(image draw.Image, bounds image.Rectangle) draw.Image {
	return &subimage{image, bounds, bounds.Sub(bounds.Min)}
}

func marginated(r image.Rectangle) image.Rectangle {
	const d = 1
	return image.Rect( r.Min.X + 1, r.Min.Y + 1, r.Max.X - 1, r.Max.Y - 1 )
}

type Gaject interface {
	GainFocus()
	LoseFocus()
	KeyStroke(int)
} 

func main() {
	w, err := x11.NewWindow()
	if err != nil {
		fmt.Println(err)
		return
	}
	draw.Draw(w.Screen(), w.Screen().Bounds(), image.White, image.ZP, draw.Over)
	events := w.EventChan()
	// pen := NewPen("LiberationMono-Regular", 0, 0, 0)
	// s := w.Screen()
	b := buffer.NewBuffer()
	es := &EditState{b, 0, 20, 0, 0}
	tick := 0
	// surf.PutString( pen, "Hollow World", -1, freetype.Pt(10, 10) )
	// 
	// ok, for routing we have a collection of rectangles, those being rectangles
	// in the coordinate space of the main window. We just see if the mouse is
	// in one of these rectangles. If it is, we say so with the associated THING
	// belonging to the rectangle. 
	r1 := image.Rect(0, 0, 100, 100)
	r2 := image.Rect(100, 100, 200, 200)
	r3 := image.Rect(100, 100, 500, 400)
	surf := (*Surface)(nil).Init(SubImage(w.Screen(), marginated(r3)), color.RGBA{255, 255, 255, 255})
	them := map[*image.Rectangle]string{&r1: "Alpha", &r2: "Beta", &r3: "Gamma"}
	//
	focus, floc := "Nowhere", (*image.Rectangle)(nil)
	for {
		e := <-events
		if e == nil {
			break
		}
		switch ee := e.(type) {

		case ui.KeyEvent:
			key := ee.Key
			if key < 0 {
				editing(surf, es, -key)
				w.FlushImage()
			}

		case ui.MouseEvent:
			tick += 1
			var loc *image.Rectangle
			here := "Nowhere"
			// fmt.Println( "tick:", tick, "event:", e )
			for k, v := range them {
				if ee.Loc.In(*k) {
					loc, here = k, v
				}
			}
			if here != focus {
				fmt.Println("leaving", focus)
				if floc != nil {
					box(w.Screen(), floc, color.White)
				}
				focus, floc = here, loc
				fmt.Println("entering", focus)
				if floc != nil {
					box(w.Screen(), floc, color.RGBA{255, 0, 0, 255})
				}
			}
			w.FlushImage()

		default:
			fmt.Printf("e: type %T, value %v\n", e, e)
		}
	}
}

func box(where draw.Image, r *image.Rectangle, c color.Color) {
	for x := r.Min.X; x < r.Max.X; x += 1 {
		where.Set(x, r.Min.Y, c)
		where.Set(x, r.Max.Y-1, c)
	}
	for y := r.Min.Y; y < r.Max.Y-1; y += 1 {
		where.Set(r.Min.X, y, c)
		where.Set(r.Max.X-1, y, c)
	}
}

var tokenRegexpString = `
    [A-Za-z_][A-Za-z0-9_]*
    | [0-9]+
    | //.*
    | " ( [^"\\] | \\[a-z][A-Z] ) * "
    | '[^"\\]|\\[a-z][A-Z]'
    | .
`

var tokenRegexp = regexp.MustCompile(purge(tokenRegexpString))

func purge(s string) string {
	bytes := make([]byte, 0, len(s))
	for _, ch := range s {
		if ch != ' ' && ch != '\n' {
			bytes = append(bytes, uint8(ch))
		}
	}
	result := string(bytes)
	// fmt.Println("squish:", result)
	return result
}

var pens = map[string]*Pen{
	"normal":  NewPen("LiberationMono-Regular", 0, 0, 0),
	"comment": NewPen("LiberationMono-Regular", 0, 255, 255),
	"id":      NewPen("LiberationMono-Regular", 0, 0, 70),
	"keyword": NewPen("LiberationMono-Bold", 120, 0, 100),
	"builtin": NewPen("LiberationMono-Regular", 0, 200, 70),
	"string":  NewPen("LiberationMono-Regular", 0, 200, 200),
}

var penFor = map[string]string{
	"true":        "builtin",
	"false":       "keyword",
	"len":         "builtin",
	"rune":        "builtin",
	"iota":        "builtin",
	"close":       "builtin",
	"int":         "builtin",
	"string":      "builtin",
	"byte":        "builtin",
	"make":        "builtin",
	"nil":         "builtin",
	"new":         "builtin",
	"append":      "builtin",
	"copy":        "builtin",
	"panic":       "builtin",
	"recover":     "builtin",
	"cap":         "builtin",
	"complex":     "builtin",
	"real":        "builtin",
	"imag":        "builtin",
	"chan":        "keyword",
	"else":        "keyword",
	"fallthrough": "keyword",
	"goto":        "keyword",
	"select":      "keyword",
	"func":        "keyword",
	"return":      "keyword",
	"type":        "keyword",
	"interface":   "keyword",
	"continue":    "keyword",
	"break":       "keyword",
	"switch":      "keyword",
	"case":        "keyword",
	"default":     "keyword",
	"const":       "keyword",
	"var":         "keyword",
	"for":         "keyword",
	"if":          "keyword",
	"map":         "keyword",
	"struct":      "keyword",
	"defer":       "keyword",
	"package":     "keyword",
	"import":      "keyword",
	"range":       "keyword",
}

func refresh_text(surf *Surface, atLine int, atCol int, text string) {
	parts := tokenRegexp.FindAllString(text, -1)
	start := freetype.Pt(0, 14+atLine*16)
	here := start
	for _, part := range parts {
		pen := pens["normal"]
		ch := part[0]
		if ch == '/' {
			pen = pens["comment"]
		}
		if unicode.IsLetter(rune(ch)) {
			pen = pens[penFor[part]]
			if pen == nil {
				pen = pens["id"]
			}
		}
		if ch == '"' || ch == '\'' {
			pen = pens["string"]
		}
		here, _ = surf.PutString(pen, part, here)
	}
	if atCol >= 0 {
		surf.PutStringOver(pens["normal"], "|", start, atCol)
	}
}

func editing(surf *Surface, es *EditState, rune int) {
	b := es.buffer
	switch rune {

	case keys.XK_Left:
		if es.Col > 0 {
			es.Col -= 1
			refresh_text(surf, es.Line, es.Col, b.Get(es.Line))
		}

	case keys.XK_Right:
		es.Col += 1
		refresh_text(surf, es.Line, es.Col, b.Get(es.Line))

	case keys.XK_Up:
		if es.Line > es.scrollOffset {
			es.Line -= 1
		} else if es.scrollOffset > 0 {
			es.scrollOffset -= 1
			es.Line -= 1
		} else {
		}

	case keys.XK_BackSpace:
		if es.Col > 0 {
			b.DeleteRune(es.Line, es.Col)
			line := b.Get(es.Line)
			refresh_text(surf, es.Line, es.Col-1, line)
			es.Col -= 1
		}

	case keys.XK_Return:
		b.Split(es.Line, es.Col)
		limit := panel.Min(b.Size(), es.Lines)
		for line := es.Line; line < limit; line += 1 {
			col := -1
			if line == es.Line+1 {
				col = 0
			}
			refresh_text(surf, line, col, b.Get(line))
		}
		es.Line += 1
		es.Col = 0

	default:
		if ' ' <= rune && rune <= 126 {
			b.Insert(rune, es.Line, es.Col)
			es.Col += 1
			refresh_text(surf, es.Line, es.Col, b.Get(es.Line))
		}
	}
}
